{}

const
  client_id  = 'wat';//'wat'; 'tst'
  client_ver = '1.0';
  api_key    = '51f5d25159da31b0814609c3a12900e2';

const
  defreq = 'http://post.audioscrobbler.com/?hs=true&p=1.2.1&c=<client-id>&v=<client-ver>&u=<user>&t=<timestamp>&a=<auth>';

function GetMD5Str(digest:TMD5Hash; buf:pAnsiChar):PAnsiChar;
begin
  buf[00]:=HexDigitChrLo[digest[00] shr 4]; buf[01]:=HexDigitChrLo[digest[00] and $0F];
  buf[02]:=HexDigitChrLo[digest[01] shr 4]; buf[03]:=HexDigitChrLo[digest[01] and $0F];
  buf[04]:=HexDigitChrLo[digest[02] shr 4]; buf[05]:=HexDigitChrLo[digest[02] and $0F];
  buf[06]:=HexDigitChrLo[digest[03] shr 4]; buf[07]:=HexDigitChrLo[digest[03] and $0F];
  buf[08]:=HexDigitChrLo[digest[04] shr 4]; buf[09]:=HexDigitChrLo[digest[04] and $0F];
  buf[10]:=HexDigitChrLo[digest[05] shr 4]; buf[11]:=HexDigitChrLo[digest[05] and $0F];
  buf[12]:=HexDigitChrLo[digest[06] shr 4]; buf[13]:=HexDigitChrLo[digest[06] and $0F];
  buf[14]:=HexDigitChrLo[digest[07] shr 4]; buf[15]:=HexDigitChrLo[digest[07] and $0F];
  buf[16]:=HexDigitChrLo[digest[08] shr 4]; buf[17]:=HexDigitChrLo[digest[08] and $0F];
  buf[18]:=HexDigitChrLo[digest[09] shr 4]; buf[19]:=HexDigitChrLo[digest[09] and $0F];
  buf[20]:=HexDigitChrLo[digest[10] shr 4]; buf[21]:=HexDigitChrLo[digest[10] and $0F];
  buf[22]:=HexDigitChrLo[digest[11] shr 4]; buf[23]:=HexDigitChrLo[digest[11] and $0F];
  buf[24]:=HexDigitChrLo[digest[12] shr 4]; buf[25]:=HexDigitChrLo[digest[12] and $0F];
  buf[26]:=HexDigitChrLo[digest[13] shr 4]; buf[27]:=HexDigitChrLo[digest[13] and $0F];
  buf[28]:=HexDigitChrLo[digest[14] shr 4]; buf[29]:=HexDigitChrLo[digest[14] and $0F];
  buf[30]:=HexDigitChrLo[digest[15] shr 4]; buf[31]:=HexDigitChrLo[digest[15] and $0F];
  buf[32]:=#0;
  result:=@buf;
end;

function GetMD5(const data;datalen:integer;var digest:TMD5Hash):TMD5Hash;
begin
  FillChar(digest,16,0);

  mir_md5_hash(pbyte(data),datalen,digest);

  result:=digest;
end;

function HandShake(login, password:PAnsiChar; notify:bool=false):bool;
var
  buf:array [0..32] of AnsiChar;
  digest:TMD5Hash;
  stat:mir_md5_state_t;
  timestamp:array [0..31] of AnsiChar;
  request:array [0..511] of AnsiChar;
  tmp,res:pAnsiChar;
begin
  result:=false;
  GetMD5Str(GetMD5(password,StrLen(password),digest),buf);
  mir_md5_init(@stat);
  mir_md5_append(@stat,@buf,32);
  IntToStr(timestamp,GetCurrentTime);
  mir_md5_append(@stat,@timestamp,StrLen(timestamp));
  mir_md5_finish(@stat,digest);
  GetMD5Str(digest,buf);
  StrCopy(request,defreq);
  StrReplace(request,'<client-id>' ,client_id);
  StrReplace(request,'<client-ver>',client_ver);
  StrReplace(request,'<user>'      ,login);
  StrReplace(request,'<timestamp>' ,timestamp);
  StrReplace(request,'<auth>'      ,buf);

  res:=SendRequest(request,REQUEST_GET);
  if (res<>nil) and (uint_ptr(res)>$0FFF) then
  begin
    if StrCmp(CharReplace(res,#10,#0),'OK')=0 then
    begin
      result:=true;
      tmp:=StrEnd(res)+1; StrDup(session_id,tmp);
      tmp:=StrEnd(tmp)+1; StrDup(np_url    ,tmp);
      tmp:=StrEnd(tmp)+1; StrDup(sub_url   ,tmp);
    end
    else if notify then
    begin
      tmp:=StrCopyE(request,Translate('Last.fm error: '));
      if      StrCmp(res,'BANNED'  )=0 then StrCopy(tmp,Translate('Client is banned'))
      else if StrCmp(res,'BADAUTH' )=0 then StrCopy(tmp,Translate('Bad Auth. Check login and password'))
      else if StrCmp(res,'BADTIME' )=0 then StrCopy(tmp,Translate('Bad TimeStamp'))
      else if StrCmp(res,'FAILED',6)=0 then StrCopy(tmp,res+7);
      CallService(MS_POPUP_SHOWMESSAGE,wparam(@request),SM_ERROR);
    end;
    mFreeMem(res);
  end;
end;

function encode(dst,src:pAnsiChar):PAnsiChar;
begin
  while src^<>#0 do
  begin
    if not (src^ in [' ','%','+','&','?',#128..#255]) then
      dst^:=src^
    else
    begin
      dst^:='%'; inc(dst);
      dst^:=HexDigitChr[ord(src^) shr 4]; inc(dst);
      dst^:=HexDigitChr[ord(src^) and $0F];
    end;
    inc(src);
    inc(dst);
  end;
  dst^:=#0;
  result:=dst;
end;

function SendNowPlaying:integer;
var
  si:pSongInfoA;
  buf    :array [0..31  ] of AnsiChar;
  args   :array [0..1023] of AnsiChar;
  res,pc:PAnsiChar;
begin
  result:=-1;
  if session_id<>nil then
  begin
    si:=pointer(CallService(MS_WAT_RETURNGLOBAL,WAT_INF_UTF8,0));

    pc:=@args;
    pc:=StrCopyE(pc,'s='); pc:=StrCopyE(pc,session_id); //'?s='
    pc:=StrCopyE(pc,'&a=');
    if si^.artist=nil then pc:=StrCopyE(pc,'Unknown')
    else                   pc:=encode(pc,si^.artist);
    pc:=StrCopyE(pc,'&t=');
    if si^.title =nil then pc:=StrCopyE(pc,'Unknown')
    else                   pc:=encode(pc,si^.title);
    pc:=StrCopyE(pc,'&l='); if si^.total>0 then pc:=StrCopyE(pc,IntToStr(buf,si^.total));
    pc:=StrCopyE(pc,'&b='); pc:=encode(pc,si^.album);
    pc:=StrCopyE(pc,'&n=');
    if si^.track<>0 then
      {pc:=}StrCopyE(pc,IntToStr(buf,si^.track));

    res:=SendRequest(np_url,REQUEST_POST,args);
    if (res<>nil) and (uint_ptr(res)>$0FFF) then
    begin
      if StrCmp(CharReplace(res,#10,#0),'OK')=0 then
        result:=1
      else if StrCmp(res,'BADSESSION')=0 then
        result:=-1;
      mFreeMem(res);
    end;
  end;
end;

function Scrobble:integer;
var
  si:pSongInfoA;
  buf,timestamp:array [0..31] of AnsiChar;
  args   :array [0..1023] of AnsiChar;
  res,pc:PAnsiChar;
begin
  result:=-1;
  if session_id<>nil then
  begin
    si:=pointer(CallService(MS_WAT_RETURNGLOBAL,WAT_INF_UTF8,0));
    IntToStr(timestamp,GetCurrentTime);

    pc:=@args;
    pc:=StrCopyE(pc,'s='  ); pc:=StrCopyE(pc,session_id);
    pc:=StrCopyE(pc,'&a[0]=');
    if si^.artist=nil then pc:=StrCopyE(pc,'Unknown')
    else                   pc:=encode(pc,si^.artist);
    pc:=StrCopyE(pc,'&t[0]=');
    if si^.title =nil then pc:=StrCopyE(pc,'Unknown')
    else                   pc:=encode(pc,si^.title);
    pc:=StrCopyE(pc,'&i[0]='); pc:=StrCopyE(pc,timestamp);
    pc:=StrCopyE(pc,'&r[0]=&m[0]=');
    pc:=StrCopyE(pc,'&l[0]=');
    if si^.total>0 then
    begin
      pc:=StrCopyE(pc,IntToStr(buf,si^.total));
      pc:=StrCopyE(pc,'&o[0]=P');
    end
    else
    begin
      pc:=StrCopyE(pc,'&o[0]=R');
    end;
    pc:=StrCopyE(pc,'&b[0]='); pc:=encode(pc,si^.album);
    pc:=StrCopyE(pc,'&n[0]=');
    if si^.track<>0 then
      {pc:=}StrCopyE(pc,IntToStr(buf,si^.track));

    res:=SendRequest(sub_url,REQUEST_POST,args);
    if (res<>nil) and (uint_ptr(res)>$0FFF) then
    begin
      if StrCmp(CharReplace(res,#10,#0),'OK')=0 then
        result:=1
      else if StrCmp(res,'BADSESSION')=0 then
      begin
        result:=-1;
      end
      else if StrCmp(res,'FAILED',6)=0 then
      begin
        StrCopy(StrCopyE(args,Translate('Last.fm error: ')),res+7);
        CallService(MS_POPUP_SHOWMESSAGE,wparam(@args),SM_NOTIFY);
        result:=0;
      end;
      mFreeMem(res);
    end;
  end;
end;

//----- Get Info service functions -----

function FullEncode(dst,src:pAnsiChar):PAnsiChar;
begin
  while src^<>#0 do
  begin
    if src^ in ['A'..'Z','a'..'z','0'..'9'] then
      dst^:=src^
    else
    begin
      dst^:='%'; inc(dst);
      dst^:=HexDigitChr[ord(src^) shr 4]; inc(dst);
      dst^:=HexDigitChr[ord(src^) and $0F];
    end;
    inc(src);
    inc(dst);
  end;
  dst^:=#0;
  result:=dst;
end;

var
  xmlparser:TXML_API_W;

function FixInfo(info:pWideChar):pWideChar;
var
  pc,ppc:pWideChar;
  cnt:cardinal;
  need:boolean;
begin
  pc:=info;
  cnt:=0;
  need:=false;
  while pc^<>#0 do
  begin
    if pc^=#$0D then
    begin
      inc(cnt);
      inc(pc);
      if pc^<>#$0A then
        need:=true;
    end
    else
      inc(pc);
  end;
  if need then
  begin
    mGetMem(result,(StrLenW(info)+1+cnt)*SizeOf(WideChar));
    pc:=info;
    ppc:=result;
    while pc^<>#0 do
    begin
      ppc^:=pc^;
      if pc^=#$0D then
      begin
        inc(ppc);
        ppc^:=#$0A;
      end;
      inc(pc);
      inc(ppc);
    end;
    ppc^:=#0;
  end
  else
    StrDupW(result,info);
end;

function GetArtistInfo(var data:tLastFMInfo;lang:integer):int;
var
  si:pSongInfo;
  res,pc:pAnsiChar;
  request:array [0..1023] of AnsiChar;
  root,actnode,node,nnode:HXML;
  i:integer;
  pcw,p,pp:PWideChar;
  artist:pAnsiChar;
begin
  result:=0;
  if data.artist=nil then
  begin
    si:=pointer(CallService(MS_WAT_RETURNGLOBAL,WAT_INF_UNICODE,0));
    pWideChar(artist):=si^.artist;
  end
  else
    pWideChar(artist):=data.artist;
  if artist=nil then
    exit;
  WideToUTF8(pWideChar(artist),artist);
  pc:=FullEncode(StrCopyE(request,
      'http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&api_key='+api_key+'&artist='),
      artist);
  mFreeMem(artist);
  if lang<>0 then
    StrCopyE(StrCopyE(pc,'&lang='),pAnsiChar(@lang));
  res:=SendRequest(request,REQUEST_GET);
  if (res<>nil) and (uint_ptr(res)>$0FFF) then
  begin
    UTF8ToWide(res,pcw);
    mFreeMem(res);
    xmlparser.cbSize:={XML_API_SIZEOF_V1;//}SizeOf(TXML_API_W);
    CallService(MS_SYSTEM_GET_XI,0,lparam(@xmlparser));
    with xmlparser do
    begin
      i:=StrLenW(pcw)*SizeOf(WideChar);
      root:=parseString(pcw,@i,nil);

      actnode:=getChild(getChild(root,0),0); // "artist"

      if data.artist=nil then
        StrDupW(data.artist,getText(GetNthChild(actnode,'name',0)));

      i:=0;
      repeat
        node:=GetNthChild(actnode,'image',i);
        if node=0 then break;
        if StrCmpW(GetAttrValue(node,'size'),'medium')=0 then
        begin
          WideToUTF8(GetText(node),data.image);
          break;
        end;
        inc(i);
      until false;

      // bio
      p:=StrPosW(pcw,'<content><![CDATA[');
      if p<>nil then
      begin
        inc(p,18);
        pp:=StrPosW(p,']]');
        if pp<> nil then pp^:=#0;
        data.info:=FixInfo(p);
      end;

      // similar
      i:=0;
      pcw:=pWideChar(@request); pcw^:=#0;
      node:=GetNthChild(actnode,'similar',0);
      repeat
        nnode:=GetNthChild(GetNthChild(node,'artist',i),'name',0);
        if nnode=0 then break;
        if pcw<>@request then
        begin
          pcw^:=','; inc(pcw);
          pcw^:=' '; inc(pcw);
        end;
          pcw:=StrCopyEW(pcw,GetText(nnode));
        inc(i);
      until false;
      pcw:=#0;
      StrDupW(data.similar,pWideChar(@request));

      // tags
      i:=0;
      pcw:=pWideChar(@request); pcw^:=#0;
      node:=GetNthChild(actnode,'tags',0);
      repeat
        nnode:=GetNthChild(GetNthChild(node,'tag',i),'name',0);
        if nnode=0 then break;
        if pcw<>@request then
        begin
          pcw^:=','; inc(pcw);
          pcw^:=' '; inc(pcw);
        end;
          pcw:=StrCopyEW(pcw,GetText(nnode));
        inc(i);
      until false;
      pcw:=#0;
      StrDupW(data.tags,pWideChar(@request));
      DestroyNode(root);
    end;
  end;
end;

function GetAlbumInfo(var data:tLastFMInfo;lang:integer):int;
var
  si:pSongInfo;
  res,pc:pAnsiChar;
  request:array [0..1023] of AnsiChar;
  root,actnode,node,nnode:HXML;
  i:integer;
  p,pp,pcw:PWideChar;
  album,artist:pAnsiChar;
begin
  result:=0;
  si:=nil;
  if data.album=nil then
  begin
    si:=pointer(CallService(MS_WAT_RETURNGLOBAL,WAT_INF_UNICODE,0));
    pWideChar(album):=si^.album;
  end
  else
    pWideChar(album):=data.album;
  if album=nil then
    exit;
  WideToUTF8(pWideChar(album),album);
  pc:=FullEncode(StrCopyE(request,
     'http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key='+api_key+'&album='),
     album);
  mFreeMem(album);
  if data.artist=nil then
  begin
    if si=nil then
      si:=pointer(CallService(MS_WAT_RETURNGLOBAL,WAT_INF_UNICODE,0));
    pWideChar(artist):=si^.artist;
  end
  else
    pWideChar(artist):=data.artist;
  if artist<>nil then
  begin
    WideToUTF8(pWideChar(artist),artist);
    pc:=FullEncode(StrCopyE(pc,'&artist='),artist);
    mFreeMem(artist);
  end;

  if lang<>0 then
    StrCopyE(StrCopyE(pc,'&lang='),pAnsiChar(@lang));

  res:=SendRequest(request,REQUEST_GET);
  if res<>nil then
  begin
    UTF8ToWide(res,pcw);
    mFreeMem(res);
    xmlparser.cbSize:={XML_API_SIZEOF_V1;//}SizeOf(TXML_API_W);
    CallService(MS_SYSTEM_GET_XI,0,lparam(@xmlparser));
    with xmlparser do
    begin
      i:=StrLenW(pcw)*SizeOf(WideChar);
      root:=parseString(pcw,@i,nil);

      actnode:=getChild(getChild(root,0),0); // "album"

      if data.album=nil then
        StrDupW(data.album,getText(GetNthChild(actnode,'name',0)));
      StrDupW(data.release,getText(GetNthChild(actnode,'releasedate',0)));
      if data.artist=nil then
        StrDupW(data.artist,getText(GetNthChild(actnode,'artist',0)));

      i:=0;
      repeat
        node:=GetNthChild(actnode,'image',i);
        if node=0 then break;
        if StrCmpW(GetAttrValue(node,'size'),'medium')=0 then
        begin
          WideToUTF8(GetText(node),data.image);
          break;
        end;
        inc(i);
      until false;

      p:=StrPosW(pcw,'<content><![CDATA[');
      if p<>nil then
      begin
        inc(p,18);
        pp:=StrPosW(p,']]');
        if pp<> nil then pp^:=#0;
        data.info:=FixInfo(p);
      end;

      // tags
      i:=0;
      pcw:=pWideChar(@request); pcw^:=#0;
      node:=GetNthChild(actnode,'toptags',0);
      repeat
        nnode:=GetNthChild(GetNthChild(node,'tag',i),'name',0);
        if nnode=0 then break;
        if pcw<>@request then
        begin
          pcw^:=','; inc(pcw);
          pcw^:=' '; inc(pcw);
        end;
          pcw:=StrCopyEW(pcw,GetText(nnode));
        inc(i);
      until false;
      pcw:=#0;
      StrDupW(data.tags,pWideChar(@request));

      DestroyNode(root);
    end;
  end;
end;

function GetTrackInfo(var data:tLastFMInfo;lang:integer):int;
var
  si:pSongInfo;
  res,pc:pAnsiChar;
  request:array [0..1023] of AnsiChar;
  root,actnode,node,anode:HXML;
  i:integer;
  p,pp,pcw:PWideChar;
  title,artist:pAnsiChar;
begin
  result:=0;
  si:=nil;
  if data.album=nil then
  begin
    si:=pointer(CallService(MS_WAT_RETURNGLOBAL,WAT_INF_UNICODE,0));
    pWideChar(title):=si^.title;
  end
  else
    pWideChar(title):=data.title;
  if title=nil then
    exit;
  WideToUTF8(pWideChar(title),title);
  pc:=FullEncode(StrCopyE(request,
     'http://ws.audioscrobbler.com/2.0/?method=track.getinfo&api_key='+api_key+'&track='),
     title);
  mFreeMem(title);
  if data.artist=nil then
  begin
    if si=nil then
      si:=pointer(CallService(MS_WAT_RETURNGLOBAL,WAT_INF_UNICODE,0));
    pWideChar(artist):=si^.artist;
  end
  else
    pWideChar(artist):=data.artist;
  if artist<>nil then
  begin
    WideToUTF8(pWideChar(artist),artist);
    pc:=FullEncode(StrCopyE(pc,'&artist='),artist);
    mFreeMem(artist);
  end;

  if lang<>0 then
    StrCopyE(StrCopyE(pc,'&lang='),pAnsiChar(@lang));

  res:=SendRequest(request,REQUEST_GET);
  if res<>nil then
  begin
    UTF8ToWide(res,pcw);
    mFreeMem(res);
    xmlparser.cbSize:={XML_API_SIZEOF_V1;//}SizeOf(TXML_API_W);
    CallService(MS_SYSTEM_GET_XI,0,lparam(@xmlparser));
    with xmlparser do
    begin
      i:=StrLenW(pcw)*SizeOf(WideChar);
      root:=parseString(pcw,@i,nil);

      actnode:=getChild(getChild(root,0),0); // "track"
      if data.artist=nil then
        StrDupW(data.artist,getText(GetNthChild(GetNthChild(actnode,'artist',0),'name',0)));

      anode:=GetNthChild(actnode,'album',i);

      if data.album=nil then
        StrDupW(data.album,getText(GetNthChild(anode,'title',0)));

      data.trknum:=StrToInt(getAttrValue(anode,'position'));
      if data.title=nil then
        StrDupW(data.title,getText(GetNthChild(actnode,'name',0)));

      i:=0;
      repeat
        node:=GetNthChild(anode,'image',i);
        if node=0 then break;
        if StrCmpW(GetAttrValue(node,'size'),'medium')=0 then
        begin
          WideToUTF8(GetText(node),data.image);
          break;
        end;
        inc(i);
      until false;

      p:=StrPosW(pcw,'<content><![CDATA[');
      if p<>nil then
      begin
        inc(p,18);
        pp:=StrPosW(p,']]');
        if pp<> nil then pp^:=#0;
        data.info:=FixInfo(p);
      end;

      // tags
      i:=0;
      pcw:=pWideChar(@request); pcw^:=#0;
      node:=GetNthChild(actnode,'toptags',0);
      repeat
        anode:=GetNthChild(GetNthChild(node,'tag',i),'name',0);
        if anode=0 then break;
        if pcw<>@request then
        begin
          pcw^:=','; inc(pcw);
          pcw^:=' '; inc(pcw);
        end;
        pcw:=StrCopyEW(pcw,GetText(anode));
        inc(i);
      until false;
      pcw:=#0;
      StrDupW(data.tags,pWideChar(@request));

      DestroyNode(root);
    end;
  end;
end;
